머신러닝이란 무엇인가?
1.1 머신러닝의 정의
머신러닝(Machine Learning)은 컴퓨터가 명시적으로 프로그래밍되지 않아도 데이터에서 패턴을 학습하여 예측이나 결정을 내리는 인공지능의 한 분야입니다. 전통적인 프로그래밍에서는 사람이 규칙을 코드로 작성하지만, 머신러닝에서는 데이터와 결과를 주면 컴퓨터가 스스로 규칙을 발견합니다.
예를 들어, "스팸 메일을 걸러라"라는 문제에서 전통 프로그래밍은 "특정 단어가 포함되면 스팸"이라는 규칙을 사람이 직접 만들지만, 머신러닝은 수천 개의 스팸/정상 메일 데이터를 보고 컴퓨터가 스스로 패턴을 찾아냅니다.
1.2 머신러닝의 세 가지 유형
| 유형 | 영문 | 설명 | 대표 알고리즘 |
|---|---|---|---|
| 지도 학습 | Supervised Learning | 입력(X)과 정답(y)이 모두 주어진 상태에서 학습 | 회귀, 분류, SVM, 랜덤 포레스트 |
| 비지도 학습 | Unsupervised Learning | 정답 없이 데이터의 구조·패턴만으로 학습 | K-Means, PCA, DBSCAN |
| 강화 학습 | Reinforcement Learning | 보상/벌칙 피드백을 통해 최적 전략 학습 | Q-Learning, DQN, PPO |
Scikit-Learn은 이 중에서 지도 학습과 비지도 학습에 특화된 라이브러리입니다. 강화 학습은 별도의 라이브러리(OpenAI Gym, Stable Baselines 등)를 사용합니다.
1.3 지도 학습 : 회귀 vs 분류
지도 학습은 예측하려는 대상(타겟, y)의 성격에 따라 두 가지로 나뉩니다.
| 구분 | 타겟 변수 | 예시 |
|---|---|---|
| 회귀 (Regression) | 연속적인 숫자 | 집값 예측, 주가 예측, 기온 예측 |
| 분류 (Classification) | 이산적인 범주/라벨 | 스팸 여부, 품종 분류, 질병 진단 |
1.4 머신러닝 작업 흐름
어떤 머신러닝 프로젝트든 아래의 기본 흐름을 따릅니다. 이 튜토리얼 전체가 이 흐름을 따라가며 각 단계를 상세히 다룹니다.
① 문제 정의 → ② 데이터 수집 → ③ 데이터 전처리 → ④ 탐색적 데이터 분석(EDA) → ⑤ 모델 선택 → ⑥ 모델 학습(fit) → ⑦ 모델 평가 → ⑧ 하이퍼파라미터 튜닝 → ⑨ 최종 예측 · 배포
1.5 핵심 용어 정리
| 용어 | 영문 | 설명 |
|---|---|---|
| 특성 (피처) | Feature | 모델에 입력되는 개별 변수 (X의 각 열) |
| 타겟 (레이블) | Target / Label | 예측하고자 하는 정답 (y) |
| 샘플 | Sample | 하나의 데이터 행 (관측치) |
| 학습 (훈련) | Training / Fit | 데이터를 이용해 모델의 파라미터를 조정하는 과정 |
| 예측 (추론) | Prediction / Inference | 학습된 모델로 새 데이터의 결과를 추정 |
| 과적합 | Overfitting | 훈련 데이터에 지나치게 맞춰져 새 데이터에 성능이 떨어지는 현상 |
| 과소적합 | Underfitting | 모델이 데이터의 패턴을 충분히 학습하지 못한 상태 |
Scikit-Learn 소개
2.1 Scikit-Learn이란?
Scikit-Learn(사이킷런)은 파이썬 기반의 오픈소스 머신러닝 라이브러리입니다. 2007년 David Cournapeau가 Google Summer of Code 프로젝트로 시작했으며, 현재는 전 세계에서 가장 널리 사용되는 범용 머신러닝 라이브러리로 자리 잡았습니다. 2025년 12월 기준 최신 버전은 1.8.0입니다.
2.2 왜 Scikit-Learn인가?
딥러닝이 유행하는 시대에도 Scikit-Learn이 가장 먼저 배워야 할 라이브러리인 이유가 있습니다.
- 일관된 API 설계 : 모든 알고리즘이
fit()→predict()→score()라는 동일한 패턴을 따릅니다. 하나를 배우면 나머지를 금방 익힙니다. - 풍부한 알고리즘 : 회귀, 분류, 군집화, 차원 축소, 전처리, 모델 선택 등 머신러닝에 필요한 거의 모든 기능을 제공합니다.
- 훌륭한 문서 : 공식 문서의 품질이 매우 높으며, 예제와 수학적 배경을 함께 설명합니다.
- NumPy/Pandas 호환 : 파이썬 데이터 과학 생태계와 완벽하게 호환됩니다.
- 실무에서 충분 : 정형 데이터(표 형태 데이터) 분석에서는 딥러닝보다 Scikit-Learn 알고리즘이 더 좋은 성능을 내는 경우가 많습니다.
2.3 Scikit-Learn의 주요 모듈
| 모듈 | 기능 | 주요 클래스/함수 |
|---|---|---|
| sklearn.datasets | 내장 데이터셋 제공 | load_iris, load_digits, make_classification |
| sklearn.preprocessing | 데이터 전처리 | StandardScaler, MinMaxScaler, LabelEncoder |
| sklearn.model_selection | 모델 선택 · 평가 | train_test_split, cross_val_score, GridSearchCV |
| sklearn.linear_model | 선형 모델 | LinearRegression, LogisticRegression, Ridge |
| sklearn.tree | 결정 트리 | DecisionTreeClassifier, DecisionTreeRegressor |
| sklearn.ensemble | 앙상블 모델 | RandomForestClassifier, GradientBoostingClassifier |
| sklearn.svm | 서포트 벡터 머신 | SVC, SVR |
| sklearn.neighbors | K-최근접 이웃 | KNeighborsClassifier, KNeighborsRegressor |
| sklearn.cluster | 군집화 | KMeans, DBSCAN, AgglomerativeClustering |
| sklearn.decomposition | 차원 축소 | PCA, TruncatedSVD |
| sklearn.metrics | 성능 평가 지표 | accuracy_score, mean_squared_error, confusion_matrix |
| sklearn.pipeline | 파이프라인 | Pipeline, make_pipeline |
2.4 Scikit-Learn의 일관된 API 패턴
Scikit-Learn의 가장 큰 강점은 어떤 알고리즘이든 동일한 패턴으로 사용한다는 점입니다. 아래 코드를 먼저 한 번 훑어보세요. 지금 이해가 안 되어도 전혀 괜찮습니다.
# 모든 scikit-learn 모델은 이 3단계를 따릅니다
# 1단계: 모델 객체 생성
model = SomeAlgorithm(hyperparameter=value)
# 2단계: 학습 (훈련 데이터를 먹여줌)
model.fit(X_train, y_train)
# 3단계: 예측 (새 데이터에 대한 결과)
predictions = model.predict(X_test)
# (선택) 성능 평가
score = model.score(X_test, y_test)
fit() → predict() → score()입니다. 이것이 Scikit-Learn이 사랑받는 핵심 이유입니다.
환경 설정
3.1 파이썬 설치 확인
Scikit-Learn은 파이썬 라이브러리이므로 먼저 파이썬이 설치되어 있어야 합니다. 파이썬 3.9 이상을 권장합니다.
# 파이썬 버전 확인
python --version
# 또는
python3 --version
3.2 Scikit-Learn 설치
# pip로 설치 (가장 일반적)
pip install scikit-learn
# 관련 라이브러리 함께 설치 (권장)
pip install numpy pandas matplotlib scikit-learn
# conda로 설치 (Anaconda 사용자)
conda install scikit-learn
# 특정 버전 설치
pip install scikit-learn==1.8.0
3.3 설치 확인
import sklearn
print(sklearn.__version__)
# 출력: 1.8.0
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
print("NumPy:", np.__version__)
print("Pandas:", pd.__version__)
print("모든 라이브러리가 정상 설치되었습니다!")
3.4 실습 환경 추천
| 환경 | 특징 | 추천 대상 |
|---|---|---|
| Jupyter Notebook | 셀 단위 실행, 시각화 편리 | 입문자, 데이터 분석 |
| Google Colab | 설치 불필요, 무료 GPU | 환경 설정이 어려운 분 |
| VS Code | 강력한 에디터, 디버깅 편리 | 개발자 |
| PyCharm | JetBrains IDE, 자동완성 우수 | 프로 개발자 |
3.5 이 튜토리얼에서 사용하는 import 모음
각 챕터에서 반복하지 않도록, 이 튜토리얼 전체에서 공통으로 사용하는 import를 미리 정리합니다.
# ===== 공통 import =====
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 데이터셋
from sklearn.datasets import (
load_iris, load_wine, load_breast_cancer,
load_diabetes, fetch_california_housing,
make_classification, make_regression, make_blobs
)
# 전처리
from sklearn.preprocessing import StandardScaler, MinMaxScaler, LabelEncoder
# 데이터 분할
from sklearn.model_selection import train_test_split
# 평가 지표
from sklearn.metrics import (
accuracy_score, precision_score, recall_score, f1_score,
confusion_matrix, classification_report,
mean_squared_error, mean_absolute_error, r2_score
)
# 시각화 설정
plt.rcParams['font.family'] = 'Malgun Gothic' # Windows 한글
plt.rcParams['axes.unicode_minus'] = False
plt.style.use('dark_background') # 어두운 배경 스타일
데이터셋 다루기
4.1 Scikit-Learn 내장 데이터셋
Scikit-Learn은 학습용 데이터셋을 기본으로 제공합니다. 별도의 파일 없이도 바로 머신러닝 실습을 시작할 수 있어 매우 편리합니다.
| 데이터셋 | 함수 | 유형 | 샘플 수 | 특성 수 | 설명 |
|---|---|---|---|---|---|
| Iris (붓꽃) | load_iris() | 분류 | 150 | 4 | 3종 붓꽃 품종 분류 |
| Wine (와인) | load_wine() | 분류 | 178 | 13 | 3종 와인 품종 분류 |
| Breast Cancer | load_breast_cancer() | 분류 | 569 | 30 | 유방암 양성/악성 분류 |
| Digits (숫자) | load_digits() | 분류 | 1,797 | 64 | 손글씨 숫자(0~9) 인식 |
| Diabetes (당뇨) | load_diabetes() | 회귀 | 442 | 10 | 당뇨 진행도 예측 |
| California Housing | fetch_california_housing() | 회귀 | 20,640 | 8 | 캘리포니아 집값 예측 |
4.2 데이터셋 로드와 구조 파악
from sklearn.datasets import load_iris
# 데이터셋 로드
iris = load_iris()
# 데이터셋 구조 확인 (Bunch 객체 = 딕셔너리와 비슷)
print("키 목록:", iris.keys())
# dict_keys(['data', 'target', 'frame', 'target_names',
# 'DESCR', 'feature_names', 'filename', 'data_module'])
# 특성 데이터 (X) - 2D 배열 (150행 × 4열)
print("data 형태:", iris.data.shape) # (150, 4)
print("data 타입:", type(iris.data)) # numpy.ndarray
# 타겟 데이터 (y) - 1D 배열 (150개)
print("target 형태:", iris.target.shape) # (150,)
print("타겟 값:", np.unique(iris.target)) # [0 1 2]
# 특성 이름과 타겟 이름
print("특성 이름:", iris.feature_names)
# ['sepal length (cm)', 'sepal width (cm)',
# 'petal length (cm)', 'petal width (cm)']
print("타겟 이름:", iris.target_names)
# ['setosa' 'versicolor' 'virginica']
# 데이터셋 설명 보기
print(iris.DESCR[:500])
data는 모델에 입력되는 특성(Feature) 데이터, 즉 X입니다. target은 예측하고자 하는 정답(Label) 데이터, 즉 y입니다. 머신러닝은 X → y의 관계를 학습하는 것입니다.
4.3 Pandas DataFrame으로 변환
NumPy 배열보다 Pandas DataFrame이 데이터 탐색에 훨씬 편합니다. 컬럼 이름도 보이고, 통계 요약도 쉽습니다.
# DataFrame으로 변환
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['target'] = iris.target
df['species'] = df['target'].map({
0: 'setosa',
1: 'versicolor',
2: 'virginica'
})
# 처음 5행 확인
print(df.head())
# 기본 통계 요약
print(df.describe())
# 정보 확인 (컬럼 타입, 결측치 등)
print(df.info())
# 클래스별 데이터 수
print(df['species'].value_counts())
4.4 인공 데이터셋 생성
특정 조건의 데이터를 직접 만들어 실험할 수 있습니다. 알고리즘의 동작을 이해하거나, 프로토타이핑에 유용합니다.
from sklearn.datasets import make_classification, make_regression, make_blobs
# 분류용 인공 데이터 (1000샘플, 10특성, 2클래스)
X, y = make_classification(
n_samples=1000,
n_features=10,
n_informative=5, # 실제 유의미한 특성 수
n_redundant=2, # 중복 특성 수
n_classes=2, # 클래스 수
random_state=42
)
print("분류 데이터:", X.shape, y.shape)
# 회귀용 인공 데이터 (500샘플, 5특성)
X, y = make_regression(
n_samples=500,
n_features=5,
noise=10, # 노이즈 정도
random_state=42
)
print("회귀 데이터:", X.shape, y.shape)
# 군집화용 데이터 (300샘플, 3개 클러스터)
X, y = make_blobs(
n_samples=300,
centers=3, # 클러스터 수
cluster_std=1.0, # 클러스터 표준편차
random_state=42
)
print("군집 데이터:", X.shape, y.shape)
4.5 외부 데이터 사용하기
# CSV 파일 로드 (Pandas 활용)
df = pd.read_csv('my_data.csv')
# 특성(X)과 타겟(y)으로 분리
X = df.drop('target_column', axis=1) # target 열을 제외한 나머지
y = df['target_column'] # target 열만
# NumPy 배열로 변환 (선택)
X = X.values
y = y.values
데이터 전처리
5.1 왜 전처리가 필요한가?
현실의 데이터는 결측치(빠진 값), 이상치(극단적으로 튀는 값), 서로 다른 스케일(키는 170cm, 몸무게는 70kg)을 가지고 있습니다. 대부분의 머신러닝 알고리즘은 깨끗하고 동일한 척도의 데이터를 입력받아야 제대로 된 성능을 발휘합니다. 데이터 전처리는 머신러닝의 성패를 좌우하는 가장 중요한 단계입니다.
5.2 Train-Test 분할
모델을 학습하는 데이터와 평가하는 데이터를 반드시 분리해야 합니다. 시험 문제를 미리 알고 공부하면 진짜 실력을 알 수 없는 것과 같은 이치입니다.
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
# 데이터 준비
iris = load_iris()
X, y = iris.data, iris.target
# 학습용 80% / 테스트용 20%로 분할
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, # 테스트 비율 (20%)
random_state=42, # 재현성을 위한 시드
stratify=y # 클래스 비율 유지 (중요!)
)
print(f"전체: {X.shape[0]}개")
print(f"학습: {X_train.shape[0]}개")
print(f"테스트: {X_test.shape[0]}개")
분류 문제에서
stratify=y를 지정하면, 학습 세트와 테스트 세트에 각 클래스(0, 1, 2)가 원본과 동일한 비율로 포함됩니다. 예를 들어, 원본에 클래스 A가 70%, B가 30%라면, 분할 후에도 같은 비율이 유지됩니다. 이를 생략하면 특정 클래스가 편향될 수 있습니다.
5.3 특성 스케일링 (Feature Scaling)
특성의 단위와 범위가 다르면 일부 알고리즘(KNN, SVM, 경사하강법 기반 모델)의 성능이 크게 저하됩니다. 예를 들어, "나이(0~100)"와 "연봉(0~100,000,000)" 두 특성이 있다면, 연봉의 숫자가 훨씬 크기 때문에 모델이 연봉에만 집중하게 됩니다.
StandardScaler (표준화)
각 특성을 평균 0, 표준편차 1로 변환합니다. 가장 많이 사용되는 스케일링 방법입니다.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
# 학습 데이터로 fit (평균, 표준편차 계산) + 변환
X_train_scaled = scaler.fit_transform(X_train)
# 테스트 데이터는 transform만! (학습 데이터의 통계로 변환)
X_test_scaled = scaler.transform(X_test)
print("스케일링 전 평균:", X_train[:, 0].mean().round(2))
print("스케일링 후 평균:", X_train_scaled[:, 0].mean().round(2))
print("스케일링 후 표준편차:", X_train_scaled[:, 0].std().round(2))
fit_transform()을 쓰면 안 됩니다. 반드시 transform()만 사용하세요. fit()은 학습 데이터의 통계(평균, 표준편차)를 기억하는 과정입니다. 테스트 데이터에도 fit하면, 미래의 정보를 미리 들여다보는 것이 되어 데이터 누출(Data Leakage)이 발생합니다.
MinMaxScaler (정규화)
모든 값을 0~1 범위로 변환합니다. 이미지 데이터나 신경망에서 자주 사용됩니다.
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_train_minmax = scaler.fit_transform(X_train)
X_test_minmax = scaler.transform(X_test)
print("최소값:", X_train_minmax.min()) # 0.0
print("최대값:", X_train_minmax.max()) # 1.0
5.4 어떤 스케일러를 사용해야 할까?
| 스케일러 | 변환 결과 | 적합한 상황 |
|---|---|---|
| StandardScaler | 평균 0, 분산 1 | 이상치가 적은 경우, 대부분의 알고리즘 |
| MinMaxScaler | 0~1 범위 | 고정된 범위가 필요한 경우, 신경망 |
| RobustScaler | 중앙값 0, IQR 기반 | 이상치가 많은 경우 |
5.5 결측치 처리
from sklearn.impute import SimpleImputer
# 평균값으로 결측치 대체
imputer = SimpleImputer(strategy='mean')
X_train_imputed = imputer.fit_transform(X_train)
X_test_imputed = imputer.transform(X_test)
# strategy 옵션들:
# 'mean' : 평균값 (숫자형 기본)
# 'median' : 중앙값 (이상치가 있을 때)
# 'most_frequent' : 최빈값 (범주형)
# 'constant' : 지정한 상수값
5.6 범주형 변수 인코딩
머신러닝 모델은 숫자만 이해합니다. 문자열로 된 범주형 변수('남', '여', '서울', '부산')는 숫자로 변환해야 합니다.
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
# ① LabelEncoder : 라벨을 0, 1, 2, ...로 변환
le = LabelEncoder()
colors = ['red', 'blue', 'green', 'red', 'blue']
encoded = le.fit_transform(colors)
print(encoded) # [2 0 1 2 0]
# 복원
print(le.inverse_transform(encoded)) # ['red' 'blue' 'green' 'red' 'blue']
# ② OneHotEncoder : 원-핫 인코딩 (더 많이 사용)
# Pandas get_dummies가 더 간편합니다
df = pd.DataFrame({'color': ['red', 'blue', 'green', 'red']})
df_encoded = pd.get_dummies(df, columns=['color'], drop_first=True)
print(df_encoded)
LabelEncoder는 순서가 있는(ordinal) 범주에, OneHotEncoder는 순서가 없는(nominal) 범주에 사용합니다. 예를 들어, 학력(초졸 < 중졸 < 고졸 < 대졸)은 LabelEncoder가 적합하고, 색상(빨강, 파랑, 초록)은 OneHotEncoder가 적합합니다. LabelEncoder로 색상을 0, 1, 2로 변환하면 모델이 "2 > 1 > 0" 즉 "초록 > 파랑 > 빨강"이라는 잘못된 순서를 학습할 수 있습니다.
학습과 예측 - 기본 흐름 완전 마스터
6.1 Scikit-Learn의 핵심 3단계
이 챕터에서는 Chapter 02에서 소개한 fit() → predict() → score() 패턴을 실제 데이터로 처음부터 끝까지 실습합니다. 이 패턴을 완벽히 이해하면 어떤 알고리즘이든 자유자재로 사용할 수 있습니다.
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
# ============================
# STEP 0 : 데이터 준비
# ============================
iris = load_iris()
X, y = iris.data, iris.target
# 학습(80%) / 테스트(20%) 분할
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# ============================
# STEP 1 : 모델 생성
# ============================
# K-최근접 이웃 분류기 (k=5)
model = KNeighborsClassifier(n_neighbors=5)
# ============================
# STEP 2 : 학습 (fit)
# ============================
# 학습 데이터를 모델에 전달하여 패턴을 학습시킵니다
model.fit(X_train, y_train)
# ============================
# STEP 3 : 예측 (predict)
# ============================
# 테스트 데이터로 예측값을 생성합니다
y_pred = model.predict(X_test)
print("실제값 :", y_test[:10])
print("예측값 :", y_pred[:10])
# ============================
# STEP 4 : 평가 (score)
# ============================
accuracy = accuracy_score(y_test, y_pred)
print(f"\n정확도: {accuracy:.4f}")
print(f"정확도: {accuracy * 100:.1f}%")
# model.score()로도 동일한 결과
print(f"score(): {model.score(X_test, y_test):.4f}")
6.2 새로운 데이터 예측
학습된 모델에 완전히 새로운 데이터를 넣어 예측해 봅시다.
# 새로운 붓꽃 데이터 (꽃받침 길이, 꽃받침 폭, 꽃잎 길이, 꽃잎 폭)
new_flower = [[5.0, 3.5, 1.5, 0.3]]
prediction = model.predict(new_flower)
print(f"예측 클래스: {prediction[0]}")
print(f"예측 품종: {iris.target_names[prediction[0]]}")
# 여러 개 동시 예측
new_flowers = [
[5.0, 3.5, 1.5, 0.3], # 아마 setosa?
[6.5, 3.0, 5.2, 2.0], # 아마 virginica?
[5.8, 2.7, 4.1, 1.0], # 아마 versicolor?
]
predictions = model.predict(new_flowers)
for flower, pred in zip(new_flowers, predictions):
print(f" {flower} → {iris.target_names[pred]}")
선형 회귀 (Linear Regression)
7.1 선형 회귀란?
선형 회귀는 입력(X)과 출력(y) 사이의 선형 관계를 찾는 가장 기본적인 회귀 알고리즘입니다. 중학교 수학에서 배운 일차 함수 y = ax + b를 떠올리면 됩니다. 데이터에 가장 잘 맞는 직선(또는 초평면)을 찾아 새로운 입력에 대한 연속적인 값을 예측합니다.
예를 들어, "집의 면적(X)"으로 "집값(y)"을 예측하거나, "공부 시간(X)"으로 "시험 점수(y)"를 예측하는 문제에 적합합니다.
7.2 실습 : 당뇨 진행도 예측
from sklearn.datasets import load_diabetes
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np
# 데이터 준비
diabetes = load_diabetes()
X, y = diabetes.data, diabetes.target
print(f"데이터 형태: X={X.shape}, y={y.shape}")
print(f"특성 이름: {diabetes.feature_names}")
# 분할
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 모델 학습
model = LinearRegression()
model.fit(X_train, y_train)
# 예측
y_pred = model.predict(X_test)
# 평가
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, y_pred)
print(f"\n=== 선형 회귀 결과 ===")
print(f"MSE (평균 제곱 오차): {mse:.2f}")
print(f"RMSE (제곱근 MSE) : {rmse:.2f}")
print(f"R² Score : {r2:.4f}")
# 회귀 계수 (각 특성의 기여도) 확인
print(f"\n절편(intercept): {model.intercept_:.2f}")
for name, coef in zip(diabetes.feature_names, model.coef_):
print(f" {name:>5s}: {coef:>8.2f}")
7.3 회귀 평가 지표 이해
| 지표 | 수식 의미 | 좋은 값 | 해석 |
|---|---|---|---|
| MSE | 오차의 제곱 평균 | 0에 가까울수록 | 큰 오차에 더 큰 벌점 |
| RMSE | MSE의 제곱근 | 0에 가까울수록 | 원래 단위로 해석 가능 |
| MAE | 오차 절대값의 평균 | 0에 가까울수록 | 이상치 영향 적음 |
| R² Score | 설명된 분산 비율 | 1에 가까울수록 | 1 = 완벽, 0 = 평균 수준 |
7.4 릿지(Ridge)와 라쏘(Lasso) 회귀
기본 선형 회귀에 규제(Regularization)를 추가한 변형입니다. 과적합을 방지하는 효과가 있습니다.
from sklearn.linear_model import Ridge, Lasso
# Ridge 회귀 (L2 규제 : 계수를 작게 유지)
ridge = Ridge(alpha=1.0)
ridge.fit(X_train, y_train)
print(f"Ridge R²: {ridge.score(X_test, y_test):.4f}")
# Lasso 회귀 (L1 규제 : 불필요한 특성의 계수를 0으로)
lasso = Lasso(alpha=1.0)
lasso.fit(X_train, y_train)
print(f"Lasso R²: {lasso.score(X_test, y_test):.4f}")
# Lasso는 특성 선택 효과가 있음
print(f"\nLasso가 0으로 만든 특성 수: {sum(lasso.coef_ == 0)}")
Ridge(릿지)는 모든 특성을 유지하되 계수 크기를 줄이고, Lasso(라쏘)는 중요하지 않은 특성의 계수를 아예 0으로 만들어 자동으로 특성 선택(Feature Selection)을 수행합니다.
alpha 값이 클수록 규제가 강합니다.
로지스틱 회귀 (Logistic Regression)
8.1 이름은 "회귀"지만 "분류" 알고리즘
로지스틱 회귀는 이름에 "회귀"가 들어가지만, 실제로는 분류 알고리즘입니다. 선형 회귀의 결과를 시그모이드(Sigmoid) 함수에 통과시켜 0~1 사이의 확률로 변환하고, 이 확률을 기반으로 클래스를 분류합니다.
예를 들어, 출력이 0.8이면 "양성(1)일 확률이 80%"로 해석하고, 0.5 이상이면 양성, 미만이면 음성으로 분류합니다.
8.2 실습 : 유방암 분류
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
# 데이터 준비
cancer = load_breast_cancer()
X, y = cancer.data, cancer.target
print(f"데이터 형태: {X.shape}")
print(f"클래스: {cancer.target_names}") # ['malignant' 'benign']
# 분할
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 스케일링 (로지스틱 회귀는 스케일링이 중요합니다)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
# 모델 학습
model = LogisticRegression(max_iter=10000, random_state=42)
model.fit(X_train_s, y_train)
# 예측 및 평가
y_pred = model.predict(X_test_s)
print(f"\n정확도: {accuracy_score(y_test, y_pred):.4f}")
print("\n상세 리포트:")
print(classification_report(y_test, y_pred,
target_names=cancer.target_names))
8.3 확률 예측
로지스틱 회귀의 강점은 단순히 클래스를 예측하는 것뿐만 아니라, 각 클래스에 속할 확률도 알려준다는 것입니다.
# predict_proba(): 각 클래스의 확률 반환
y_proba = model.predict_proba(X_test_s)
# 처음 5개 샘플의 확률
for i in range(5):
print(f"샘플 {i}: 악성={y_proba[i][0]:.4f}, "
f"양성={y_proba[i][1]:.4f} → "
f"예측={cancer.target_names[y_pred[i]]}")
K-최근접 이웃 (KNN)
9.1 KNN의 직관적 이해
KNN(K-Nearest Neighbors)은 가장 직관적인 머신러닝 알고리즘입니다. "주변에 어떤 이웃이 많은가?"를 보고 판단합니다. 새로운 데이터가 들어오면, 학습 데이터 중 가장 가까운 K개의 이웃을 찾아 다수결로 분류합니다.
일상에서의 비유: 새로 이사 온 동네에서 "이 근처에 맛집이 많으면 이 동네는 맛집 동네일 거야"라고 판단하는 것과 같습니다.
9.2 핵심 하이퍼파라미터
| 파라미터 | 설명 | 기본값 | 팁 |
|---|---|---|---|
| n_neighbors | 이웃의 수 (K) | 5 | 홀수 권장 (동점 방지) |
| weights | 이웃의 가중치 | 'uniform' | 'distance'면 가까운 이웃에 더 큰 가중치 |
| metric | 거리 계산 방식 | 'minkowski' | 유클리디안(p=2), 맨해튼(p=1) |
9.3 실습과 K값에 따른 성능 변화
from sklearn.datasets import load_wine
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 와인 데이터 준비
wine = load_wine()
X, y = wine.data, wine.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 스케일링 (KNN은 거리 기반이므로 반드시 스케일링!)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
# K값 1~20까지 변화시키며 정확도 비교
k_range = range(1, 21)
scores = []
for k in k_range:
knn = KNeighborsClassifier(n_neighbors=k)
knn.fit(X_train_s, y_train)
score = knn.score(X_test_s, y_test)
scores.append(score)
print(f"K={k:2d} → 정확도: {score:.4f}")
best_k = list(k_range)[scores.index(max(scores))]
print(f"\n최적 K: {best_k} (정확도: {max(scores):.4f})")
결정 트리 (Decision Tree)
10.1 결정 트리의 직관
결정 트리는 스무고개 게임과 같습니다. "이 데이터의 특성 A가 5보다 큰가?" → "특성 B가 3 이하인가?" 처럼 예/아니오 질문을 반복하여 최종 결론에 도달합니다. 사람이 이해하기 가장 쉬운 알고리즘이며, 시각적으로 결과를 표현할 수 있습니다.
10.2 실습 : Iris 분류
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
# 데이터 준비
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 결정 트리 모델 (깊이 3으로 제한하여 과적합 방지)
tree = DecisionTreeClassifier(
max_depth=3, # 트리의 최대 깊이
min_samples_split=5, # 분할에 필요한 최소 샘플 수
random_state=42
)
tree.fit(X_train, y_train)
# 예측 및 평가
y_pred = tree.predict(X_test)
print(f"정확도: {accuracy_score(y_test, y_pred):.4f}")
# 트리 구조를 텍스트로 출력
tree_rules = export_text(tree, feature_names=iris.feature_names)
print("\n결정 트리 규칙:")
print(tree_rules)
10.3 특성 중요도 (Feature Importance)
결정 트리의 큰 장점 중 하나는 각 특성이 예측에 얼마나 기여하는지 알려준다는 것입니다.
# 특성 중요도 확인
importances = tree.feature_importances_
for name, imp in sorted(
zip(iris.feature_names, importances),
key=lambda x: x[1],
reverse=True
):
print(f" {name:20s}: {imp:.4f} {'█' * int(imp * 50)}")
max_depth, min_samples_split, min_samples_leaf 등의 파라미터로 트리의 복잡도를 제한하세요.
앙상블 - 랜덤 포레스트
11.1 앙상블이란?
앙상블(Ensemble)은 "여러 모델의 예측을 결합하여 더 나은 결과를 얻는 기법"입니다. 한 명의 전문가보다 여러 전문가의 의견을 종합하면 더 정확한 판단을 할 수 있는 것과 같은 원리입니다. 이를 "집단 지성(Wisdom of Crowds)"이라고도 합니다.
11.2 랜덤 포레스트
랜덤 포레스트(Random Forest)는 여러 개의 결정 트리를 만들어 다수결(분류) 또는 평균(회귀)으로 최종 결과를 내는 앙상블 기법입니다. 각 트리는 데이터의 일부(Bootstrap)와 특성의 일부를 무작위로 사용하기 때문에 다양한 관점을 제공합니다.
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
# 데이터 준비
cancer = load_breast_cancer()
X, y = cancer.data, cancer.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 랜덤 포레스트 (100개의 트리)
rf = RandomForestClassifier(
n_estimators=100, # 트리 개수
max_depth=10, # 각 트리의 최대 깊이
min_samples_split=5, # 분할 최소 샘플
random_state=42,
n_jobs=-1 # 모든 CPU 코어 사용
)
rf.fit(X_train, y_train)
# 평가
y_pred = rf.predict(X_test)
print(f"랜덤 포레스트 정확도: {accuracy_score(y_test, y_pred):.4f}")
print(classification_report(y_test, y_pred,
target_names=cancer.target_names))
# 상위 10개 중요 특성
print("=== 특성 중요도 TOP 10 ===")
feat_imp = sorted(
zip(cancer.feature_names, rf.feature_importances_),
key=lambda x: x[1], reverse=True
)
for name, imp in feat_imp[:10]:
print(f" {name:25s}: {imp:.4f}")
11.3 그래디언트 부스팅
랜덤 포레스트가 여러 트리를 "동시에" 만들었다면, 그래디언트 부스팅은 이전 트리의 실수를 보완하는 트리를 "순차적으로" 추가합니다. 일반적으로 랜덤 포레스트보다 높은 성능을 내지만, 학습 시간이 더 오래 걸립니다.
from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(
n_estimators=100,
learning_rate=0.1, # 학습률 (작을수록 신중, 느림)
max_depth=3,
random_state=42
)
gb.fit(X_train, y_train)
print(f"그래디언트 부스팅 정확도: {gb.score(X_test, y_test):.4f}")
서포트 벡터 머신 (SVM)
12.1 SVM의 핵심 아이디어
SVM(Support Vector Machine)은 두 클래스를 가장 "넓은 간격(마진)"으로 분리하는 경계선(결정 경계)을 찾는 알고리즘입니다. 두 클래스 사이에 가능한 한 넓은 도로를 깔아서, 새로운 데이터가 어느 쪽에 속하는지 판단하는 것으로 비유할 수 있습니다.
핵심은 "커널 트릭(Kernel Trick)"입니다. 직선으로 분리할 수 없는 데이터도 고차원 공간으로 매핑하면 분리할 수 있게 됩니다.
12.2 실습
from sklearn.svm import SVC
from sklearn.datasets import load_wine
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
wine = load_wine()
X, y = wine.data, wine.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# SVM은 반드시 스케일링!
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
# 다양한 커널 비교
kernels = ['linear', 'rbf', 'poly']
for kernel in kernels:
svm = SVC(kernel=kernel, random_state=42)
svm.fit(X_train_s, y_train)
score = svm.score(X_test_s, y_test)
print(f" 커널: {kernel:8s} → 정확도: {score:.4f}")
C는 규제 파라미터로, 값이 클수록 마진 위반을 허용하지 않습니다(과적합 위험). gamma는 RBF 커널의 영향 범위를 조절합니다. kernel은 'linear'(선형), 'rbf'(가우시안), 'poly'(다항식) 등을 선택할 수 있습니다. SVM은 대량 데이터(수만 건 이상)에서는 학습이 느릴 수 있습니다.
모델 평가 지표 완전 정리
13.1 분류 평가 지표
"정확도 98%면 좋은 모델일까요?" 반드시 그렇지는 않습니다. 100명 중 1명만 암 환자인 데이터에서, "모두 정상"이라고 예측하면 정확도는 99%이지만, 정작 암 환자는 하나도 찾지 못합니다. 상황에 맞는 평가 지표를 선택하는 것이 중요합니다.
| 지표 | 설명 | 수식 의미 | 중요한 경우 |
|---|---|---|---|
| Accuracy (정확도) | 전체 중 맞춘 비율 | (TP+TN) / 전체 | 클래스 균형일 때 |
| Precision (정밀도) | 양성 예측 중 실제 양성 | TP / (TP+FP) | 오탐(스팸 필터) |
| Recall (재현율) | 실제 양성 중 맞춘 비율 | TP / (TP+FN) | 미탐(암 진단) |
| F1 Score | Precision과 Recall의 조화평균 | 2 × P×R / (P+R) | 불균형 데이터 |
13.2 혼동 행렬 (Confusion Matrix)
from sklearn.metrics import (
confusion_matrix, classification_report,
accuracy_score, precision_score, recall_score, f1_score
)
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target,
test_size=0.2, random_state=42, stratify=cancer.target
)
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)
y_pred = rf.predict(X_test)
# 혼동 행렬
cm = confusion_matrix(y_test, y_pred)
print("혼동 행렬:")
print(cm)
# 상세 리포트 (한 번에 모든 지표 확인)
print("\n분류 리포트:")
print(classification_report(y_test, y_pred,
target_names=['악성', '양성']))
13.3 회귀 평가 지표 코드
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
import numpy as np
# y_test, y_pred가 있다고 가정
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"MSE : {mse:.4f}")
print(f"RMSE : {rmse:.4f}")
print(f"MAE : {mae:.4f}")
print(f"R² : {r2:.4f}")
암 진단 → Recall (놓치면 안 됨, 미탐 최소화)
스팸 필터 → Precision (정상 메일을 스팸으로 보내면 안 됨, 오탐 최소화)
불균형 데이터 → F1 Score (Precision과 Recall의 균형)
집값 예측 → RMSE, R² Score
교차 검증 (Cross Validation)
14.1 왜 교차 검증이 필요한가?
단순히 한 번의 Train-Test 분할로 평가하면, 그 특정 분할에 운 좋게 잘 맞는 결과가 나올 수 있습니다. 교차 검증은 데이터를 여러 번 다르게 나누어 평가함으로써, 모델의 일반화 성능을 더 신뢰성 있게 측정합니다.
14.2 K-Fold 교차 검증
데이터를 K개의 조각(Fold)으로 나누고, 각 조각을 한 번씩 테스트 세트로 사용하여 K번 학습-평가를 반복합니다. 최종 점수는 K번의 평균입니다.
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
import numpy as np
iris = load_iris()
X, y = iris.data, iris.target
model = RandomForestClassifier(n_estimators=100, random_state=42)
# 5-Fold 교차 검증
scores = cross_val_score(
model, X, y,
cv=5, # 5개 Fold
scoring='accuracy' # 평가 지표
)
print("각 Fold의 정확도:", scores)
print(f"평균 정확도: {scores.mean():.4f}")
print(f"표준 편차 : {scores.std():.4f}")
print(f"신뢰 구간 : {scores.mean():.4f} ± {scores.std() * 2:.4f}")
14.3 여러 모델 비교
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
models = {
'Logistic Regression': LogisticRegression(max_iter=10000),
'KNN (k=5)' : KNeighborsClassifier(n_neighbors=5),
'Decision Tree' : DecisionTreeClassifier(random_state=42),
'Random Forest' : RandomForestClassifier(random_state=42),
'SVM (RBF)' : SVC(random_state=42),
}
print(f"{'모델':25s} {'평균':>8s} {'표준편차':>8s}")
print("-" * 45)
for name, model in models.items():
scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
print(f"{name:25s} {scores.mean():>8.4f} {scores.std():>8.4f}")
분류 문제에서
cross_val_score는 기본적으로 StratifiedKFold를 사용합니다. 이는 각 Fold에서 클래스 비율이 원본과 동일하게 유지되도록 합니다. 회귀 문제에서는 일반 KFold가 사용됩니다.
하이퍼파라미터 튜닝
15.1 하이퍼파라미터란?
하이퍼파라미터(Hyperparameter)는 모델이 학습하기 전에 사람이 직접 설정해야 하는 값입니다. 예를 들어, KNN의 K값, 랜덤 포레스트의 트리 개수, SVM의 C값 등입니다. 이 값을 어떻게 설정하느냐에 따라 모델 성능이 크게 달라집니다.
15.2 GridSearchCV - 모든 조합 탐색
가능한 모든 하이퍼파라미터 조합을 시도하여 가장 좋은 조합을 찾습니다. 확실하지만, 조합이 많으면 시간이 오래 걸립니다.
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
# 데이터 준비
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target,
test_size=0.2, random_state=42, stratify=cancer.target
)
# 탐색할 하이퍼파라미터 조합 정의
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [3, 5, 10, None],
'min_samples_split': [2, 5, 10],
}
# 총 조합 수: 3 × 4 × 3 = 36가지
# GridSearchCV 실행
grid_search = GridSearchCV(
estimator=RandomForestClassifier(random_state=42),
param_grid=param_grid,
cv=5, # 5-Fold 교차 검증
scoring='accuracy',
n_jobs=-1, # 전체 CPU 사용
verbose=1 # 진행 상황 출력
)
grid_search.fit(X_train, y_train)
# 결과 확인
print(f"\n최적 파라미터: {grid_search.best_params_}")
print(f"최고 교차검증 점수: {grid_search.best_score_:.4f}")
print(f"테스트 세트 점수: {grid_search.score(X_test, y_test):.4f}")
# 최적 모델 바로 사용
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test)
15.3 RandomizedSearchCV - 무작위 탐색
모든 조합 대신 무작위로 일부만 시도합니다. 파라미터 범위가 넓을 때 효율적입니다.
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform
# 파라미터 분포 정의 (범위에서 랜덤 샘플링)
param_dist = {
'n_estimators': randint(50, 500),
'max_depth': randint(2, 20),
'min_samples_split': randint(2, 20),
'min_samples_leaf': randint(1, 10),
}
random_search = RandomizedSearchCV(
estimator=RandomForestClassifier(random_state=42),
param_distributions=param_dist,
n_iter=50, # 50번만 무작위 시도
cv=5,
scoring='accuracy',
random_state=42,
n_jobs=-1
)
random_search.fit(X_train, y_train)
print(f"최적 파라미터: {random_search.best_params_}")
print(f"최고 점수: {random_search.best_score_:.4f}")
파라미터 조합이 적을 때(수십~수백 개)는 GridSearchCV, 많을 때(수천 개 이상)는 RandomizedSearchCV를 사용하세요. 실무에서는 RandomizedSearchCV로 대략적인 범위를 좁힌 뒤, GridSearchCV로 미세 조정하는 2단계 전략이 효과적입니다.
파이프라인 (Pipeline)
16.1 왜 파이프라인이 필요한가?
실제 머신러닝 작업에서는 전처리(스케일링, 인코딩) → 모델 학습 → 예측의 여러 단계를 거칩니다. 이 단계들을 일일이 따로 실행하면 코드가 복잡해지고, 실수(특히 데이터 누출)가 발생하기 쉽습니다.
파이프라인(Pipeline)은 이 모든 단계를 하나의 객체로 묶어서, fit() 한 번, predict() 한 번으로 모든 처리가 자동으로 이루어지게 합니다.
16.2 기본 파이프라인
from sklearn.pipeline import Pipeline, make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split, cross_val_score
wine = load_wine()
X, y = wine.data, wine.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 파이프라인 생성: 스케일링 → SVM
pipe = Pipeline([
('scaler', StandardScaler()), # 1단계: 스케일링
('svm', SVC(kernel='rbf')) # 2단계: 모델
])
# 또는 간단히 make_pipeline 사용
pipe = make_pipeline(StandardScaler(), SVC(kernel='rbf'))
# fit 한 번으로 전처리 + 학습 완료!
pipe.fit(X_train, y_train)
# predict 한 번으로 전처리 + 예측 완료!
score = pipe.score(X_test, y_test)
print(f"파이프라인 정확도: {score:.4f}")
# 교차 검증도 파이프라인 그대로 사용
cv_scores = cross_val_score(pipe, X, y, cv=5)
print(f"교차검증 평균: {cv_scores.mean():.4f}")
16.3 파이프라인 + GridSearchCV
파이프라인의 진정한 위력은 GridSearchCV와 결합할 때 나타납니다. 전처리와 모델의 파라미터를 동시에 튜닝할 수 있습니다.
from sklearn.model_selection import GridSearchCV
pipe = Pipeline([
('scaler', StandardScaler()),
('svm', SVC())
])
# 파이프라인 내부 파라미터는 '단계이름__파라미터명' 형태로 접근
param_grid = {
'svm__C': [0.1, 1, 10, 100],
'svm__kernel': ['rbf', 'linear'],
'svm__gamma': ['scale', 'auto', 0.01, 0.1],
}
grid = GridSearchCV(pipe, param_grid, cv=5, scoring='accuracy', n_jobs=-1)
grid.fit(X_train, y_train)
print(f"최적 파라미터: {grid.best_params_}")
print(f"최고 점수: {grid.best_score_:.4f}")
print(f"테스트 점수: {grid.score(X_test, y_test):.4f}")
파이프라인을 사용하면 교차 검증 시 각 Fold마다 전처리가 독립적으로 수행되어 데이터 누출을 완벽히 방지합니다. 파이프라인 없이 전체 데이터에 먼저
fit_transform()을 한 뒤 교차 검증을 하면, 테스트 Fold의 정보가 스케일링 통계에 포함되는 미묘한 데이터 누출이 발생합니다.
비지도 학습 - 군집화 (Clustering)
17.1 비지도 학습이란?
지금까지 배운 알고리즘은 모두 정답(y)이 있는 지도 학습이었습니다. 비지도 학습은 정답 없이 데이터의 구조와 패턴을 스스로 발견합니다. 대표적인 비지도 학습 기법이 군집화(Clustering)입니다.
군집화는 비슷한 데이터끼리 그룹으로 묶는 것입니다. 고객 세분화, 이미지 분할, 이상치 탐지 등에 활용됩니다.
17.2 K-Means 군집화
K-Means는 가장 대표적인 군집화 알고리즘으로, 데이터를 K개의 그룹으로 나눕니다. 각 그룹의 중심(centroid)을 반복적으로 이동시키며 최적의 그룹을 찾습니다.
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
import numpy as np
# 인공 데이터 생성 (3개 클러스터)
X, y_true = make_blobs(
n_samples=300, centers=3,
cluster_std=1.0, random_state=42
)
# 스케일링
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# K-Means 모델
kmeans = KMeans(
n_clusters=3, # 클러스터 수
init='k-means++', # 초기 중심 설정 방법
n_init=10, # 초기화 반복 횟수
max_iter=300, # 최대 반복 횟수
random_state=42
)
kmeans.fit(X_scaled)
# 결과 확인
print("클러스터 라벨:", np.unique(kmeans.labels_))
print("클러스터별 데이터 수:")
for i in range(3):
print(f" 클러스터 {i}: {(kmeans.labels_ == i).sum()}개")
print(f"관성(Inertia): {kmeans.inertia_:.2f}")
17.3 최적의 K 찾기 - 엘보우 방법
K를 몇으로 설정해야 할까요? 엘보우(Elbow) 방법은 K를 1부터 늘려가며 관성(Inertia, 클러스터 내 거리 합)을 측정하고, 관성이 급격히 감소하다가 완만해지는 "팔꿈치" 지점을 찾는 방법입니다.
# 엘보우 방법으로 최적 K 탐색
inertias = []
K_range = range(1, 11)
for k in K_range:
km = KMeans(n_clusters=k, random_state=42, n_init=10)
km.fit(X_scaled)
inertias.append(km.inertia_)
print(f"K={k:2d} → Inertia: {km.inertia_:.2f}")
# 시각화
plt.figure(figsize=(8, 4))
plt.plot(K_range, inertias, 'o-', color='#f57c00')
plt.xlabel('K (클러스터 수)')
plt.ylabel('Inertia')
plt.title('엘보우 방법')
plt.grid(True, alpha=0.3)
plt.show()
17.4 실루엣 점수
실루엣 점수(Silhouette Score)는 군집화 품질을 -1~1 사이의 값으로 평가합니다. 1에 가까울수록 군집이 잘 분리되었다는 의미입니다.
from sklearn.metrics import silhouette_score
for k in range(2, 8):
km = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = km.fit_predict(X_scaled)
sil = silhouette_score(X_scaled, labels)
print(f"K={k} → 실루엣 점수: {sil:.4f}")
차원 축소 - PCA
18.1 차원의 저주와 차원 축소
특성(차원)이 너무 많으면 오히려 모델 성능이 떨어지는 현상을 "차원의 저주(Curse of Dimensionality)"라고 합니다. 고차원 공간에서는 데이터 간 거리가 비슷해지고, 학습에 필요한 데이터 양이 기하급수적으로 늘어납니다.
차원 축소(Dimensionality Reduction)는 원래 데이터의 중요한 정보는 유지하면서 특성 수를 줄이는 기법입니다.
18.2 PCA (주성분 분석)
PCA(Principal Component Analysis)는 가장 대표적인 차원 축소 기법입니다. 데이터의 분산(퍼짐 정도)을 가장 잘 설명하는 새로운 축(주성분)을 찾아, 이 축으로 데이터를 투영합니다.
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_breast_cancer
import numpy as np
# 30개 특성을 가진 유방암 데이터
cancer = load_breast_cancer()
X = cancer.data
print(f"원본 특성 수: {X.shape[1]}") # 30
# PCA 전 반드시 스케일링
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# PCA로 30차원 → 2차원 축소
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
print(f"축소 후 특성 수: {X_pca.shape[1]}") # 2
# 각 주성분이 설명하는 분산 비율
print(f"\n주성분별 설명 분산 비율:")
for i, ratio in enumerate(pca.explained_variance_ratio_):
print(f" PC{i+1}: {ratio:.4f} ({ratio*100:.1f}%)")
print(f" 합계: {pca.explained_variance_ratio_.sum():.4f}")
18.3 PCA + 분류 모델
from sklearn.pipeline import make_pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
# PCA 적용 전후 성능 비교
# 원본 (30차원)
pipe_full = make_pipeline(
StandardScaler(),
RandomForestClassifier(n_estimators=100, random_state=42)
)
score_full = cross_val_score(pipe_full, cancer.data, cancer.target, cv=5)
# PCA 적용 (10차원으로 축소)
pipe_pca = make_pipeline(
StandardScaler(),
PCA(n_components=10),
RandomForestClassifier(n_estimators=100, random_state=42)
)
score_pca = cross_val_score(pipe_pca, cancer.data, cancer.target, cv=5)
print(f"원본 (30차원): {score_full.mean():.4f}")
print(f"PCA (10차원): {score_pca.mean():.4f}")
PCA는 반드시 스케일링 후에 적용해야 합니다. 스케일링 없이 PCA를 적용하면, 값이 큰 특성이 주성분을 지배합니다. 또한 PCA로 변환된 주성분은 원래 특성의 해석이 어려워지므로, 해석 가능성이 중요한 경우에는 신중하게 사용하세요.
실전 프로젝트 - 캘리포니아 집값 예측
지금까지 배운 모든 내용을 종합하여, 실전 머신러닝 프로젝트를 처음부터 끝까지 진행합니다. 캘리포니아 주의 인구조사 데이터를 기반으로 집값을 예측하는 회귀 문제입니다.
19.1 데이터 로드 및 탐색
import numpy as np
import pandas as pd
from sklearn.datasets import fetch_california_housing
# 데이터 로드
housing = fetch_california_housing()
df = pd.DataFrame(housing.data, columns=housing.feature_names)
df['MedHouseVal'] = housing.target # 타겟: 중간 집값 (단위: $100,000)
print("=== 데이터셋 정보 ===")
print(f"크기: {df.shape}")
print(f"\n특성 이름: {housing.feature_names}")
print(f"\n처음 5행:")
print(df.head())
print(f"\n기본 통계:")
print(df.describe().round(2))
print(f"\n결측치:")
print(df.isnull().sum())
19.2 데이터 분할 및 전처리
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# X, y 분리
X = df.drop('MedHouseVal', axis=1)
y = df['MedHouseVal']
# 분할
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"학습 세트: {X_train.shape}")
print(f"테스트 세트: {X_test.shape}")
# 스케일링
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
19.3 여러 모델 학습 및 비교
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.svm import SVR
from sklearn.metrics import mean_squared_error, r2_score
models = {
'Linear Regression' : LinearRegression(),
'Ridge (α=1)' : Ridge(alpha=1.0),
'Lasso (α=0.01)' : Lasso(alpha=0.01),
'Decision Tree' : DecisionTreeRegressor(max_depth=10, random_state=42),
'Random Forest' : RandomForestRegressor(n_estimators=100,
max_depth=15, random_state=42, n_jobs=-1),
'Gradient Boosting' : GradientBoostingRegressor(n_estimators=200,
max_depth=5, learning_rate=0.1, random_state=42),
'SVR (RBF)' : SVR(kernel='rbf', C=10),
}
print(f"{'모델':25s} {'RMSE':>8s} {'R²':>8s}")
print("-" * 45)
results = {}
for name, model in models.items():
model.fit(X_train_s, y_train)
y_pred = model.predict(X_test_s)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
results[name] = {'rmse': rmse, 'r2': r2}
print(f"{name:25s} {rmse:>8.4f} {r2:>8.4f}")
19.4 최적 모델 튜닝
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform
# Gradient Boosting 튜닝
param_dist = {
'n_estimators': randint(100, 500),
'max_depth': randint(3, 10),
'learning_rate': uniform(0.01, 0.19),
'min_samples_split': randint(2, 20),
'subsample': uniform(0.7, 0.3),
}
random_search = RandomizedSearchCV(
GradientBoostingRegressor(random_state=42),
param_distributions=param_dist,
n_iter=30,
cv=5,
scoring='r2',
random_state=42,
n_jobs=-1
)
random_search.fit(X_train_s, y_train)
print(f"최적 파라미터: {random_search.best_params_}")
print(f"최고 CV R²: {random_search.best_score_:.4f}")
# 최종 평가
best_model = random_search.best_estimator_
y_pred_best = best_model.predict(X_test_s)
final_rmse = np.sqrt(mean_squared_error(y_test, y_pred_best))
final_r2 = r2_score(y_test, y_pred_best)
print(f"\n최종 테스트 RMSE: {final_rmse:.4f}")
print(f"최종 테스트 R² : {final_r2:.4f}")
19.5 특성 중요도 분석
# 최적 모델의 특성 중요도
importances = best_model.feature_importances_
feat_imp = sorted(
zip(housing.feature_names, importances),
key=lambda x: x[1],
reverse=True
)
print("=== 특성 중요도 ===")
for name, imp in feat_imp:
bar = '█' * int(imp * 50)
print(f" {name:12s}: {imp:.4f} {bar}")
19.6 프로젝트 결론
이번 프로젝트에서 7개 모델을 비교한 결과, Gradient Boosting이 가장 좋은 성능을 보였습니다. 하이퍼파라미터 튜닝을 통해 R² 점수를 약 0.83까지 끌어올렸으며, 중간 소득(MedInc)이 집값 예측에 가장 중요한 특성임을 확인했습니다. 실전에서는 여기에 추가적으로 특성 공학(Feature Engineering), 이상치 처리, 더 고도화된 앙상블 기법(XGBoost, LightGBM) 등을 적용하면 성능을 더 높일 수 있습니다.
부록 - 치트시트 & 학습 로드맵
20.1 Scikit-Learn 핵심 코드 치트시트
# =============================================
# Scikit-Learn 치트시트 - 한눈에 보는 핵심 코드
# =============================================
# ── 1. 데이터 분할 ──
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# ── 2. 전처리 ──
from sklearn.preprocessing import StandardScaler, MinMaxScaler
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train) # 학습: fit + transform
X_test_s = scaler.transform(X_test) # 테스트: transform만!
# ── 3. 핵심 3단계 ──
model = SomeAlgorithm(params) # 생성
model.fit(X_train, y_train) # 학습
y_pred = model.predict(X_test) # 예측
# ── 4. 분류 알고리즘 ──
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.svm import SVC
# ── 5. 회귀 알고리즘 ──
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.svm import SVR
# ── 6. 비지도 학습 ──
from sklearn.cluster import KMeans, DBSCAN
from sklearn.decomposition import PCA
# ── 7. 분류 평가 지표 ──
from sklearn.metrics import (
accuracy_score, # 정확도
precision_score, # 정밀도
recall_score, # 재현율
f1_score, # F1
confusion_matrix, # 혼동 행렬
classification_report # 종합 리포트
)
# ── 8. 회귀 평가 지표 ──
from sklearn.metrics import (
mean_squared_error, # MSE
mean_absolute_error, # MAE
r2_score # R² Score
)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
# ── 9. 교차 검증 ──
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
# ── 10. 하이퍼파라미터 튜닝 ──
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
grid = GridSearchCV(model, param_grid, cv=5, scoring='accuracy')
grid.fit(X_train, y_train)
grid.best_params_ # 최적 파라미터
grid.best_score_ # 최고 교차검증 점수
grid.best_estimator_ # 최적 모델 객체
# ── 11. 파이프라인 ──
from sklearn.pipeline import make_pipeline
pipe = make_pipeline(StandardScaler(), RandomForestClassifier())
pipe.fit(X_train, y_train)
pipe.score(X_test, y_test)
# ── 12. 모델 저장/불러오기 ──
import joblib
joblib.dump(model, 'model.pkl') # 저장
loaded_model = joblib.load('model.pkl') # 불러오기
20.2 알고리즘 선택 가이드
| 상황 | 추천 알고리즘 | 이유 |
|---|---|---|
| 빠른 베이스라인 (분류) | LogisticRegression | 빠르고 해석 가능 |
| 빠른 베이스라인 (회귀) | LinearRegression, Ridge | 빠르고 해석 가능 |
| 정형 데이터 최고 성능 | GradientBoosting, XGBoost, LightGBM | 대부분의 대회에서 우승 |
| 해석 가능성 중요 | DecisionTree, LogisticRegression | 결과를 설명하기 쉬움 |
| 데이터가 적음 (<1000) | SVM, KNN | 소량 데이터에서도 잘 작동 |
| 특성이 매우 많음 | Lasso, PCA + 모델 | 자동 특성 선택/축소 |
| 고객 세분화 | KMeans, DBSCAN | 비지도 군집화 |
| 이미지/텍스트/음성 | 딥러닝 (PyTorch, TF) | 비정형 데이터에 강함 |
20.3 자주 하는 실수 & 해결법
| 실수 | 결과 | 해결법 |
|---|---|---|
| 테스트 데이터에 fit_transform() | 데이터 누출 (성능 과대평가) | 테스트는 반드시 transform()만 |
| 스케일링 없이 KNN/SVM 사용 | 성능 저하 | StandardScaler 적용 |
| stratify 미사용 (불균형 데이터) | 편향된 분할 | stratify=y 지정 |
| 과적합 무시 | 테스트 성능 급락 | 교차 검증, 규제(Ridge/Lasso), max_depth 제한 |
| 정확도만 보기 | 불균형 데이터에서 착각 | Precision, Recall, F1, 혼동행렬 확인 |
| 결측치 무시 | 에러 또는 잘못된 학습 | SimpleImputer로 처리 |
| 범주형 변수 미변환 | 에러 | OneHotEncoder 또는 get_dummies() |
| random_state 미지정 | 재현 불가능한 결과 | 항상 random_state 설정 |
| 전체 데이터로 스케일링 후 분할 | 미묘한 데이터 누출 | 파이프라인 사용 |
20.4 학습 로드맵
| 단계 | 기간 | 학습 내용 | 이 튜토리얼 |
|---|---|---|---|
| Level 1 입문 | 1~2주 | 머신러닝 개념, 데이터 준비, fit/predict 패턴 | Ch.1 ~ Ch.6 |
| Level 2 기초 | 2~4주 | 회귀, 분류 알고리즘, 평가 지표 | Ch.7 ~ Ch.13 |
| Level 3 중급 | 1~2개월 | 교차 검증, 튜닝, 파이프라인, 비지도 학습 | Ch.14 ~ Ch.18 |
| Level 4 실전 | 2~3개월 | 실전 프로젝트, Kaggle 대회 참가 | Ch.19 ~ Ch.20 |
| Level 5 고급 | 지속 | XGBoost/LightGBM, 특성 공학, 딥러닝(PyTorch) | 추가 학습 |
20.5 마치며
축하합니다! 전 20장의 Scikit-Learn 완전정복 튜토리얼을 모두 마치셨습니다.
Scikit-Learn은 머신러닝의 기본기를 다지기에 최적의 라이브러리입니다. 일관된 API 패턴 덕분에 하나의 알고리즘을 마스터하면 나머지를 배우는 속도가 비약적으로 빨라집니다. 이 튜토리얼의 예제를 직접 실행하고 변형해 보면서 반복 학습하세요.
1. 데이터를 이해하세요 — 아무리 좋은 알고리즘도 쓰레기 데이터에서는 쓰레기 결과를 냅니다. 전처리와 탐색적 분석에 전체 시간의 60~80%를 투자하세요.
2. 단순한 모델부터 시작하세요 — 항상 선형 회귀/로지스틱 회귀로 베이스라인을 잡고, 점진적으로 복잡한 모델을 시도하세요. 단순한 모델이 충분히 좋을 때가 많습니다.
3. 과적합을 경계하세요 — 훈련 정확도 99%가 아니라 테스트 정확도가 중요합니다. 교차 검증을 습관화하고, 훈련 점수와 테스트 점수의 차이를 항상 확인하세요.
데이터 과학의 세계에 오신 것을 환영합니다. 이 튜토리얼이 여러분의 머신러닝 여정에 든든한 첫걸음이 되기를 바랍니다!